<?php


namespace BoojasrFramework\ServicesContainer;


use BoojasrFramework\PackageManage;
use BoojasrFramework\ServicesContainer\DependencyInjection\Compiler\CompilerPassInterface;
use BoojasrFramework\ServicesContainer\DependencyInjection\Compiler\PassContainer;
use BoojasrFramework\DependencyInjection\Compiler\RegisteredEventPass;
use BoojasrFramework\DependencyInjection\Compiler\RoutePass;
use BoojasrFramework\ServicesContainer\Entity\Definition;
use Symfony\Component\Yaml\Yaml;
use \BoojasrFramework\Kernel as SystemKernel;

class Compiler
{
    private $passContainer;

    public function __construct()
    {
        $this->passContainer = new PassContainer();
    }

    /**
     * 编译容器
     * @param ContainerInterface $container
     */
    public function compile($container){
        // 编译描述文件
        $this->compileDefinition($container);

        // 编译容器
        $container->addCompilerPass(new RegisteredEventPass()); // 注册事件处理器
        $container->addCompilerPass(new RoutePass()); // 路由处理器

        (new PackageManage())->build($container);

        // 处理编译器，进行初始化容器编译
        $this->processContainer($container);
    }

    /**
     * 处理容器
     * @param $container
     */
    public function processContainer($container){
        try {
            foreach ($this->passContainer->getPasses() as $pass) {
                if($pass instanceof CompilerPassInterface){
                    $pass->process($container);
                }
            }
        }catch (\Exception $e){
        }
    }

    /**
     * 编译描述文件：扫描所有模块下的'*.service.yaml'
     * @param $container
     */
    public function compileDefinition($container){
        $config = SystemKernel::getConfig();
        $servicePaths = $config["service_paths"]??[];
        $servicesConfigPathList = [
            dirname(__DIR__)."/../Kernel/KernelServices.yml"
        ];
        if ($servicePaths){
            $servicesConfigPathList = array_merge($servicePaths,$servicesConfigPathList);
        }
        foreach ($servicesConfigPathList as $servicesConfigPath){
            $definitions = null;
            if (file_exists($servicesConfigPath)){
                if (!$definitions){
                    $definitions = Yaml::parseFile($servicesConfigPath);
                }
                $this->proCompileDefinition($container,$definitions);
            }
        }
    }

    /**
     * 预处理编译描述文件
     * @param ContainerBuilder $container
     * @param $definitions
     */
    public function proCompileDefinition($container,$definitions){
        if(isset($definitions["services"])&&!empty($definitions["services"])){
            $services = $definitions["services"];
            $definitions = [];
            foreach ($services as $id => $service){
                $definition = Definition::fromArray($service);
                if ($definition->isPublic() == false||$definition->isPrivate()){
                    $container->addRemoveIds($id);
                    if ($definition->isPrivate()){
                        $container->addPrivates($id);
                    }
                }
                if (isset($service["tags"])&&!empty($service["tags"])){
                    $tags = $service["tags"];
                    $tagName = $tags["name"];
                    if ($tagName){
                        unset($tags["name"]);
                        $definition->addTag($tagName,$tags);
                    }
                }
                $definitions[$id] = $definition;
//                $definitions[$definition->getClass()] = $definition;
            }
            $container->addDefinitions($definitions);
        }
    }


    /**
     * 编译服务
     * @param ContainerInterface $container
     * @param $id
     * @param false $isConstructorArgument
     * @return false|object
     * @throws \ReflectionException
     */
    public function compileService($container, $id, $isConstructorArgument = false){
        if (!$container instanceof ContainerBuilder){
            return false;
        }
        $definition = $container->getDefinition($id);
        if(!$definition){
            return false;
        }

        $class = $definition->getClass();

        $r = new \ReflectionClass($class);
        $arguments = $definition->getArguments();

        if ($arguments){
            // 处理依赖
            $arguments = $this->doResolveServices($arguments,$container,$isConstructorArgument);
        }
        $tmpService = $container->get($id,false);
        if ($tmpService){
            $service = $tmpService;
        }else{
            $service = null === $r->getConstructor() ? $r->newInstance() : $r->newInstanceArgs(array_values($arguments));
        }

        $fields = $definition->getFields();

        if ($fields){
            // 处理字段依赖注入
            $fields = $this->doResolveServices($fields,$container,$isConstructorArgument);
            foreach ($fields as $fName=>$value){
                if ($r->hasProperty($fName)){
                    $property = $r->getProperty($fName);
                    if ($property->isPublic()){
                        // 如果是公共方法，即直接注入
                        $property->setValue($service,$value);
                    }else{
                        // 如果为非共方法，则开放访问权限注入，在注入完成后关闭外部访问权限。
                        $property->setAccessible(true);
                        $property->setValue($service,$value);
                        $property->setAccessible(false);
                    }
                }
            }
        }

        $calls = $definition->getMethodCalls();
        if ($calls){
            foreach ($calls as $call){
                $result = call_user_func_array([$service,$call[0]],$call[1]);
            }
        }

        $container->set($id,$service);

        return $service;
    }

    /**
     *
     * 依赖注入
     *
     * @param $value
     * @param $container
     * @param false $isConstructorArgument
     * @return false|mixed|object
     * @throws \ReflectionException
     */
    protected function doResolveServices($value,$container,$isConstructorArgument = false){
        if (\is_array($value)) {
            foreach ($value as $k => $v) {
                $value[$k] = $this->doResolveServices($v, $container, $isConstructorArgument);
            }
        }elseif (is_string($value)){
            if($value[0] == "@"){
                $serviceName = substr($value,1);
                /** @var ContainerBuilder $container */
                if ( $container->hasDefinition($serviceName)){
                    $value = $this->compileService($container,$serviceName,true);
                }
            }
        }

        return $value;
    }
    /**
     * @return PassContainer
     */
    public function getPassContainer()
    {
        return $this->passContainer;
    }

    public function addPass(CompilerPassInterface $pass, $type = PassContainer::TYPE_BEFORE_OPTIMIZATION, $priority = 0)
    {
        $this->passContainer->addPass($pass, $type, $priority);
    }

}
